#include <stdio.h>
#include <cv.h>
#include <highgui.h>
#include <getopt.h>

#define WINDOW "Preview"

// domyślne ustawienia
int _DEVICE = CV_CAP_ANY; // numer urządzania
int _PREVIEW = 0; // podgląd
int _OBJECT_SIZE = 60; // minimalny rozmiar wykrytego obszaru
int _RECTANGLES = 2; // liczba przecinających się obszarów
float _SCALE_FACTOR = 1.2; // współczynnik skalowania w kolejnych krokach wyszukiwania
char* _DETECTOR = "haarcascade_frontalface_alt.xml"; // plik klasyfikatora Haara
int _HISTORY = 20; // liczba zapamiętywanych wyników
int _THRESHOLD = 11; // ile razy wynik musi się powtórzyć w historii przed zatwierdzeniem

int detectFaces(IplImage* frame, CvMemStorage* storage, CvHaarClassifierCascade* classifier);
void visitorCount();
void printUsage();

int main(int argc, char* argv[]) {
	int c;

	if(argc == 2 && (strcmp(argv[1], "help") == 0 || strcmp(argv[1], "--help") == 0)) {
		printUsage();
		return 0;
	}

	while((c = getopt(argc, argv, "d:f:r:s:z:h:t:p")) != -1) {
		switch(c) {
			case 'd':
				_DEVICE = atoi(optarg);
				break;
			case 'f':
				_DETECTOR = optarg;
				break;
			case 'r':
				_RECTANGLES = atoi(optarg);
				break;
			case 's':
				_OBJECT_SIZE = atoi(optarg);
				break;
			case 'z':
				_SCALE_FACTOR = atof(optarg);
				break;
			case 'p':
				_PREVIEW = 1;
				break;
			case 't':
				_THRESHOLD = atoi(optarg);
				break;
			case 'h':
				_HISTORY = atoi(optarg);
				break;
			case '?':
				fprintf(stderr, "Invalid command or no argument specified. Type \"visitorcount --help\" to view valid commands.\n", optopt);
				exit(1);
			default:
				abort();
		}
	}

	visitorCount();

	return 0;
}

void printUsage() {
	fprintf(stderr, "Usage: visitorcount <commands>\n"
			"Valid commands are\n"
			"\t-d\tCamera device number. Value X of /dev/videoX . E.g. -d 0\n"
			"\t-f\tCliassifier filename. E.g. -f haarcascade_frontalface_alt.xml\n"
			"\t-r\tNumber of rectangles' intersections required to classify area as a face. E.g. -r 2\n"
			"\t-s\tObject size. E.g. -s 40\n"
			"\t-z\tScale factor. E.g. -z 1.2\n"
			"\t-p\tShow preview. E.g. -p\n"
			"\t-h\tNumber of stored results in history. E.g. -h 20\n"
			"\t-t\tNumber of occurences of certain result in history before acceptance (t <= h). E.g. -t 11\n"
		);
}

void visitorCount() {
	int i, visitors, count, last = 0;
	IplImage* tempFrame;
	CvMemStorage* facesMemStorage;
	int history[_HISTORY]; // HISTORY ostatnich wyników, działa na zasadzie round-robin
	int hindex = 0; // indeks historii

	CvCapture* pCapture = cvCaptureFromCAM(_DEVICE);

	if(!pCapture) {
		printf("-2\n");
		fflush(stdout);
		fprintf(stderr, "Failed to initialize video capture\n");
		exit(1);
	}

	CvHaarClassifierCascade* classifier = (CvHaarClassifierCascade*)cvLoad(_DETECTOR, 0, 0, 0);

	if(!classifier) {
		printf("-1\n");
		fflush(stdout);
		fprintf(stderr, "ERROR: Invalid classifier data.\n");
		exit(1);
	}

	for(i = 0; i < 25; ++i)
		cvQueryFrame(pCapture);

	facesMemStorage = cvCreateMemStorage(0);

	if(_PREVIEW)
		cvNamedWindow(WINDOW, 1);

	tempFrame = NULL;

	while(1) {
		IplImage* currentFrame = cvQueryFrame(pCapture);

		if(!currentFrame) {
			printf("-2\n");
			fflush(stdout);
			fprintf(stderr, "Failed to get a video frame\n");
			exit(1);
		}

		if(!tempFrame) {
			tempFrame = cvCreateImage(cvSize(currentFrame->width, currentFrame->height), IPL_DEPTH_8U, currentFrame->nChannels);
		}

		if(currentFrame->origin == IPL_ORIGIN_TL) {
			cvCopy(currentFrame, tempFrame, 0);
		} else {
			cvFlip(currentFrame, tempFrame, 0);
		}

		visitors = detectFaces(tempFrame, facesMemStorage, classifier);
		// zapamiętanie wyniku
		history[(hindex++)%_HISTORY] = visitors;
		// jeżeli dana wartość była wyświetlona, kolejne pojawienie się może jedynie zwiększyć liczbę wystąpieć
		// w przeciwnym przypadku liczymy wystąpienia
		if(last != visitors) {
			// sprawdzenie ile razy w historii występuje wykryta liczba
			count = 0;
			for(i = 0; i < _HISTORY; ++i) {
				if(history[(hindex+i)%_HISTORY] == visitors) {
					++count;
				}
			}

			if(count >= _THRESHOLD) {
				printf("%d\n", visitors);
				fflush(stdout);
				last = visitors; 
			}
		}

		if(_PREVIEW) {
			cvShowImage(WINDOW, tempFrame);

			if(cvWaitKey(10) >= 0) {
				break;
			}
		}
	}

	if(_PREVIEW) {
		cvDestroyWindow(WINDOW);
	}
	cvReleaseCapture(&pCapture);
	cvReleaseMemStorage(&facesMemStorage);
	if(tempFrame) {
		cvReleaseImage(&tempFrame);
	}
}

int detectFaces(IplImage* frame, CvMemStorage* storage, CvHaarClassifierCascade* classifier) {
	int i;
	CvSeq* faces = cvHaarDetectObjects(frame, classifier, storage, _SCALE_FACTOR, _RECTANGLES, CV_HAAR_DO_CANNY_PRUNING, cvSize(_OBJECT_SIZE, _OBJECT_SIZE));

	if(faces) {
		if(_PREVIEW) {
			for(i = 0; i < faces->total; ++i) {
				CvPoint point1, point2;
				CvRect* rectangle = (CvRect*)cvGetSeqElem(faces, i);
				point1.x = rectangle->x;
				point2.x = rectangle->x + rectangle->width;
				point1.y = rectangle->y;
				point2.y = rectangle->y + rectangle->height;
				cvRectangle(frame, point1, point2, CV_RGB(255,0,0), 3, 8, 0);
			}
		}
		return faces->total;
	}

	return 0;
}
